package com.ternnetwork.oa.service.impl.note;

import com.ternnetwork.commons.model.http.BaseResponse;
import com.ternnetwork.commons.utils.ExtendedStringUtils;
import com.ternnetwork.commons.utils.Md5Utils;
import com.ternnetwork.core.accesscontroll.entity.User;
import com.ternnetwork.core.accesscontroll.service.UserService;

import com.ternnetwork.oa.dto.note.NoteDto;
import com.ternnetwork.oa.dto.note.NoteQueryDto;
import com.ternnetwork.oa.entity.note.Note;
import com.ternnetwork.oa.entity.note.NoteCategory;
import com.ternnetwork.oa.enums.messages.NoteMessages;
import com.ternnetwork.oa.repository.note.NoteCategoryRepository;
import com.ternnetwork.oa.repository.note.NoteRepository;
import com.ternnetwork.oa.service.note.NoteService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
public class NoteServiceImpl implements NoteService {

    @Autowired
    private NoteRepository noteRepository;

    @Autowired
    private NoteCategoryRepository noteCategoryRepository;

    @Autowired
    private UserService userService;

    @Autowired
    private EntityManager entityManager;

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
    public BaseResponse add(NoteDto dto, String username){

        BaseResponse  baseResponse=checkProperties(dto);

        if(baseResponse.getStatus().intValue()==0) {

            return baseResponse;
        }

        Note note=new Note();

        BeanUtils.copyProperties(dto,note);

        if(dto.getCategoryId()!=null){
            NoteCategory noteCategory=noteCategoryRepository.getOne(dto.getCategoryId());
            note.setCategory(noteCategory);
        }
        User loginUser=userService.findByName(username);

        note.setCreatedById(loginUser.getId());

        noteRepository.saveAndFlush(note);

        baseResponse.setSccucess(note.getId());

        return baseResponse;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
    public BaseResponse update(NoteDto dto, String username){

        BaseResponse  baseResponse=checkProperties(dto);

        if(baseResponse.getStatus().intValue()==0) {

            return baseResponse;
        }

        Optional<Note> optional=noteRepository.findById(dto.getId());

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }
        Note note=optional.get();

        User loginUser=userService.findByName(username);

        if(note.getCreatedById()!=loginUser.getId()){

            baseResponse.setFailedForObjectNotBelongsToLoginUser();

            return baseResponse;
        }

        if(dto.getCategoryId()!=null){
            NoteCategory noteCategory=noteCategoryRepository.getOne(dto.getCategoryId());
            note.setCategory(noteCategory);
        }
        BeanUtils.copyProperties(dto,note);

        note.setUpdatedById(loginUser.getId());

        note.setUpdateTime(new Date());

        noteRepository.saveAndFlush(note);

        baseResponse.setSccucess();

        return baseResponse;
    }



    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
    public BaseResponse deleteById(Long id,String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Note> optional=noteRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Note note=optional.get();

        User loginUser=userService.findByName(username);

        if(note.getCreatedById()!=loginUser.getId()){

            baseResponse.setFailedForObjectNotBelongsToLoginUser();

            return baseResponse;
        }

        noteRepository.delete(note);

        baseResponse.setSccucess();

        return baseResponse;
    }

    @Override
    public BaseResponse findByPage(Pageable pageable,NoteQueryDto dto, String username) {

        BaseResponse  baseResponse=new BaseResponse();

        User loginUser=userService.findByName(username);

        dto.setCreatedById(loginUser.getId());

        CriteriaBuilder criteriaBuilder =this.entityManager.getCriteriaBuilder();

        CriteriaQuery<Note> criteriaQuery=criteriaBuilder.createQuery(Note.class);

        Root<Note> root=criteriaQuery.from(Note.class);

        //tell it what field we need here
        criteriaQuery.multiselect(root.get("id"),
                        root.get("title"),
                        root.get("createTime"),
                        root.get("updateTime"),
                        root.get("shareCode"))
                .where(getPredicate(dto,root,criteriaBuilder));

        List<Note> noteList=this.entityManager.createQuery(criteriaQuery)
                .setFirstResult((int)pageable.getOffset())
                .setMaxResults(pageable.getPageSize()).getResultList();

        Page<Note> page= PageableExecutionUtils.getPage(noteList,pageable,()->this.noteRepository.count(getSpecification(dto)));

        baseResponse.setSccucess(page);

        return baseResponse;
    }

    private Specification getSpecification(NoteQueryDto dto){

        return (root, criteriaQuery, criteriaBuilder) -> {

            return criteriaQuery.where(getPredicate(dto,root,criteriaBuilder)).getRestriction();
        };

    }

    private Predicate[]  getPredicate(NoteQueryDto dto, Root<Note> root, CriteriaBuilder criteriaBuilder){

        List<Predicate> predicates = new ArrayList<>();

        if(!StringUtils.isEmpty(dto.getTitle())){

            predicates.add(criteriaBuilder.like(root.get("title").as(String.class),"%".concat(dto.getTitle()).concat("%")));
        }

        if(dto.getCategoryId()!=null){

            predicates.add(criteriaBuilder.equal(root.get("category").get("id").as(Long.class),dto.getCategoryId()));
        }

        predicates.add(criteriaBuilder.equal(root.get("createdById").as(Long.class),dto.getCreatedById()));

        Predicate[] predicateArray = new Predicate[predicates.size()];

        return predicates.toArray(predicateArray);
    }


    private BaseResponse checkProperties (NoteDto t) {

        BaseResponse  baseResponse=new BaseResponse();

        if(StringUtils.isEmpty(t.getTitle())) {

            baseResponse.setFailed(NoteMessages.FAILED_MESSAGE_TITLE_CAN_NOT_BE_EMPTY.getName());

            return baseResponse;
        }

        baseResponse.setSccucess();

        return baseResponse;
    }


    public BaseResponse getOne(Long id,String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Note> optional=noteRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Note note=optional.get();

        User loginUser=userService.findByName(username);

        if(note.getCreatedById()!=loginUser.getId()){

            baseResponse.setFailedForObjectNotBelongsToLoginUser();

            return baseResponse;
        }

        baseResponse.setSccucess(note);

        return baseResponse;
    }

    public BaseResponse addShare(Long id, String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Note> optional=noteRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Note note=optional.get();

        User loginUser=userService.findByName(username);

        if(note.getCreatedById()!=loginUser.getId()){

            baseResponse.setFailedForObjectNotBelongsToLoginUser();

            return baseResponse;
        }

        note.setShareTime(new Date());

        String shareCode=getShareCode(note);

        note.setShareCode(shareCode);

        noteRepository.saveAndFlush(note);

        baseResponse.setSccucess(shareCode);

        return baseResponse;
    }

    public BaseResponse deleteShare(Long id, String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Note> optional=noteRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Note note=optional.get();

        User loginUser=userService.findByName(username);

        if(note.getCreatedById()!=loginUser.getId()){

            baseResponse.setFailedForObjectNotBelongsToLoginUser();

            return baseResponse;
        }

        note.setShareTime(null);

        note.setShareCode(null);

        noteRepository.saveAndFlush(note);

        baseResponse.setSccucess();

        return baseResponse;
    }


    public BaseResponse getShareOne(String shareCode){

        BaseResponse  baseResponse=new BaseResponse();

        List<Note> noteList= noteRepository.findAllByShareCode(shareCode);

        if(noteList.size()==0){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Note note=noteList.get(0);

        baseResponse.setSccucess(note);

        return baseResponse;
    }


    private String getShareCode(Note note){

        return Md5Utils.MD5(String.valueOf(note.getId()).concat("-").concat(String.valueOf(note.getCreatedById())));
    }




}
